import {Vector3} from '../math/Vector3.js';
import {Box3} from '../math/Box3.js';
import {EventDispatcher} from './EventDispatcher.js';
import {BufferAttribute, Float32BufferAttribute, Uint16BufferAttribute, Uint32BufferAttribute} from './BufferAttribute.js';
import {Sphere} from '../math/Sphere.js';
import {DirectGeometry} from './DirectGeometry.js';
import {Object3D} from './Object3D.js';
import {Matrix4} from '../math/Matrix4.js';
import {Matrix3} from '../math/Matrix3.js';
import {MathUtils} from '../math/MathUtils.js';
import {arrayMax} from '../utils.js';

let _bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id

const _m1 = new Matrix4();
const _obj = new Object3D();
const _offset = new Vector3();
const _box = new Box3();
const _boxMorphTargets = new Box3();
const _vector = new Vector3();

function BufferGeometry() {

  Object.defineProperty(this, 'id', {value: _bufferGeometryId += 2});

  this.uuid = MathUtils.generateUUID();

  this.name = '';
  this.type = 'BufferGeometry';

  this.index = null;
  this.attributes = {};

  this.morphAttributes = {};
  this.morphTargetsRelative = false;

  this.groups = [];

  this.boundingBox = null;
  this.boundingSphere = null;

  this.drawRange = {start: 0, count: Infinity};

  this.userData = {};

}

BufferGeometry.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

  constructor: BufferGeometry,

  isBufferGeometry: true,

  getIndex: function () {

    return this.index;

  },

  setIndex: function (index) {

    if (Array.isArray(index)) {

      this.index = new (arrayMax(index) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute)(index, 1);

    } else {

      this.index = index;

    }

  },

  getAttribute: function (name) {

    return this.attributes[name];

  },

  setAttribute: function (name, attribute) {

    this.attributes[name] = attribute;

    return this;

  },

  deleteAttribute: function (name) {

    delete this.attributes[name];

    return this;

  },

  addGroup: function (start, count, materialIndex) {

    this.groups.push({

      start: start,
      count: count,
      materialIndex: materialIndex !== undefined ? materialIndex : 0

    });

  },

  clearGroups: function () {

    this.groups = [];

  },

  setDrawRange: function (start, count) {

    this.drawRange.start = start;
    this.drawRange.count = count;

  },

  applyMatrix4: function (matrix) {

    const position = this.attributes.position;

    if (position !== undefined) {

      position.applyMatrix4(matrix);

      position.needsUpdate = true;

    }

    const normal = this.attributes.normal;

    if (normal !== undefined) {

      const normalMatrix = new Matrix3().getNormalMatrix(matrix);

      normal.applyNormalMatrix(normalMatrix);

      normal.needsUpdate = true;

    }

    const tangent = this.attributes.tangent;

    if (tangent !== undefined) {

      tangent.transformDirection(matrix);

      tangent.needsUpdate = true;

    }

    if (this.boundingBox !== null) {

      this.computeBoundingBox();

    }

    if (this.boundingSphere !== null) {

      this.computeBoundingSphere();

    }

    return this;

  },

  rotateX: function (angle) {

    // rotate geometry around world x-axis

    _m1.makeRotationX(angle);

    this.applyMatrix4(_m1);

    return this;

  },

  rotateY: function (angle) {

    // rotate geometry around world y-axis

    _m1.makeRotationY(angle);

    this.applyMatrix4(_m1);

    return this;

  },

  rotateZ: function (angle) {

    // rotate geometry around world z-axis

    _m1.makeRotationZ(angle);

    this.applyMatrix4(_m1);

    return this;

  },

  translate: function (x, y, z) {

    // translate geometry

    _m1.makeTranslation(x, y, z);

    this.applyMatrix4(_m1);

    return this;

  },

  scale: function (x, y, z) {

    // scale geometry

    _m1.makeScale(x, y, z);

    this.applyMatrix4(_m1);

    return this;

  },

  lookAt: function (vector) {

    _obj.lookAt(vector);

    _obj.updateMatrix();

    this.applyMatrix4(_obj.matrix);

    return this;

  },

  center: function () {

    this.computeBoundingBox();

    this.boundingBox.getCenter(_offset).negate();

    this.translate(_offset.x, _offset.y, _offset.z);

    return this;

  },

  setFromObject: function (object) {

    // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this );

    const geometry = object.geometry;

    if (object.isPoints || object.isLine) {

      const positions = new Float32BufferAttribute(geometry.vertices.length * 3, 3);
      const colors = new Float32BufferAttribute(geometry.colors.length * 3, 3);

      this.setAttribute('position', positions.copyVector3sArray(geometry.vertices));
      this.setAttribute('color', colors.copyColorsArray(geometry.colors));

      if (geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length) {

        const lineDistances = new Float32BufferAttribute(geometry.lineDistances.length, 1);

        this.setAttribute('lineDistance', lineDistances.copyArray(geometry.lineDistances));

      }

      if (geometry.boundingSphere !== null) {

        this.boundingSphere = geometry.boundingSphere.clone();

      }

      if (geometry.boundingBox !== null) {

        this.boundingBox = geometry.boundingBox.clone();

      }

    } else if (object.isMesh) {

      if (geometry && geometry.isGeometry) {

        this.fromGeometry(geometry);

      }

    }

    return this;

  },

  setFromPoints: function (points) {

    const position = [];

    for (let i = 0, l = points.length; i < l; i++) {

      const point = points[i];
      position.push(point.x, point.y, point.z || 0);

    }

    this.setAttribute('position', new Float32BufferAttribute(position, 3));

    return this;

  },

  updateFromObject: function (object) {

    let geometry = object.geometry;

    if (object.isMesh) {

      let direct = geometry.__directGeometry;

      if (geometry.elementsNeedUpdate === true) {

        direct = undefined;
        geometry.elementsNeedUpdate = false;

      }

      if (direct === undefined) {

        return this.fromGeometry(geometry);

      }

      direct.verticesNeedUpdate = geometry.verticesNeedUpdate;
      direct.normalsNeedUpdate = geometry.normalsNeedUpdate;
      direct.colorsNeedUpdate = geometry.colorsNeedUpdate;
      direct.uvsNeedUpdate = geometry.uvsNeedUpdate;
      direct.groupsNeedUpdate = geometry.groupsNeedUpdate;

      geometry.verticesNeedUpdate = false;
      geometry.normalsNeedUpdate = false;
      geometry.colorsNeedUpdate = false;
      geometry.uvsNeedUpdate = false;
      geometry.groupsNeedUpdate = false;

      geometry = direct;

    }

    if (geometry.verticesNeedUpdate === true) {

      const attribute = this.attributes.position;

      if (attribute !== undefined) {

        attribute.copyVector3sArray(geometry.vertices);
        attribute.needsUpdate = true;

      }

      geometry.verticesNeedUpdate = false;

    }

    if (geometry.normalsNeedUpdate === true) {

      const attribute = this.attributes.normal;

      if (attribute !== undefined) {

        attribute.copyVector3sArray(geometry.normals);
        attribute.needsUpdate = true;

      }

      geometry.normalsNeedUpdate = false;

    }

    if (geometry.colorsNeedUpdate === true) {

      const attribute = this.attributes.color;

      if (attribute !== undefined) {

        attribute.copyColorsArray(geometry.colors);
        attribute.needsUpdate = true;

      }

      geometry.colorsNeedUpdate = false;

    }

    if (geometry.uvsNeedUpdate) {

      const attribute = this.attributes.uv;

      if (attribute !== undefined) {

        attribute.copyVector2sArray(geometry.uvs);
        attribute.needsUpdate = true;

      }

      geometry.uvsNeedUpdate = false;

    }

    if (geometry.lineDistancesNeedUpdate) {

      const attribute = this.attributes.lineDistance;

      if (attribute !== undefined) {

        attribute.copyArray(geometry.lineDistances);
        attribute.needsUpdate = true;

      }

      geometry.lineDistancesNeedUpdate = false;

    }

    if (geometry.groupsNeedUpdate) {

      geometry.computeGroups(object.geometry);
      this.groups = geometry.groups;

      geometry.groupsNeedUpdate = false;

    }

    return this;

  },

  fromGeometry: function (geometry) {

    geometry.__directGeometry = new DirectGeometry().fromGeometry(geometry);

    return this.fromDirectGeometry(geometry.__directGeometry);

  },

  fromDirectGeometry: function (geometry) {

    const positions = new Float32Array(geometry.vertices.length * 3);
    this.setAttribute('position', new BufferAttribute(positions, 3).copyVector3sArray(geometry.vertices));

    if (geometry.normals.length > 0) {

      const normals = new Float32Array(geometry.normals.length * 3);
      this.setAttribute('normal', new BufferAttribute(normals, 3).copyVector3sArray(geometry.normals));

    }

    if (geometry.colors.length > 0) {

      const colors = new Float32Array(geometry.colors.length * 3);
      this.setAttribute('color', new BufferAttribute(colors, 3).copyColorsArray(geometry.colors));

    }

    if (geometry.uvs.length > 0) {

      const uvs = new Float32Array(geometry.uvs.length * 2);
      this.setAttribute('uv', new BufferAttribute(uvs, 2).copyVector2sArray(geometry.uvs));

    }

    if (geometry.uvs2.length > 0) {

      const uvs2 = new Float32Array(geometry.uvs2.length * 2);
      this.setAttribute('uv2', new BufferAttribute(uvs2, 2).copyVector2sArray(geometry.uvs2));

    }

    // groups

    this.groups = geometry.groups;

    // morphs

    for (const name in geometry.morphTargets) {

      const array = [];
      const morphTargets = geometry.morphTargets[name];

      for (let i = 0, l = morphTargets.length; i < l; i++) {

        const morphTarget = morphTargets[i];

        const attribute = new Float32BufferAttribute(morphTarget.data.length * 3, 3);
        attribute.name = morphTarget.name;

        array.push(attribute.copyVector3sArray(morphTarget.data));

      }

      this.morphAttributes[name] = array;

    }

    // skinning

    if (geometry.skinIndices.length > 0) {

      const skinIndices = new Float32BufferAttribute(geometry.skinIndices.length * 4, 4);
      this.setAttribute('skinIndex', skinIndices.copyVector4sArray(geometry.skinIndices));

    }

    if (geometry.skinWeights.length > 0) {

      const skinWeights = new Float32BufferAttribute(geometry.skinWeights.length * 4, 4);
      this.setAttribute('skinWeight', skinWeights.copyVector4sArray(geometry.skinWeights));

    }

    //

    if (geometry.boundingSphere !== null) {

      this.boundingSphere = geometry.boundingSphere.clone();

    }

    if (geometry.boundingBox !== null) {

      this.boundingBox = geometry.boundingBox.clone();

    }

    return this;

  },

  computeBoundingBox: function () {

    if (this.boundingBox === null) {

      this.boundingBox = new Box3();

    }

    const position = this.attributes.position;
    const morphAttributesPosition = this.morphAttributes.position;

    if (position && position.isGLBufferAttribute) {

      console.error('THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this);

      this.boundingBox.set(
        new Vector3(-Infinity, -Infinity, -Infinity),
        new Vector3(+Infinity, +Infinity, +Infinity)
      );

      return;

    }

    if (position !== undefined) {

      this.boundingBox.setFromBufferAttribute(position);

      // process morph attributes if present

      if (morphAttributesPosition) {

        for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {

          const morphAttribute = morphAttributesPosition[i];
          _box.setFromBufferAttribute(morphAttribute);

          if (this.morphTargetsRelative) {

            _vector.addVectors(this.boundingBox.min, _box.min);
            this.boundingBox.expandByPoint(_vector);

            _vector.addVectors(this.boundingBox.max, _box.max);
            this.boundingBox.expandByPoint(_vector);

          } else {

            this.boundingBox.expandByPoint(_box.min);
            this.boundingBox.expandByPoint(_box.max);

          }

        }

      }

    } else {

      this.boundingBox.makeEmpty();

    }

    if (isNaN(this.boundingBox.min.x) || isNaN(this.boundingBox.min.y) || isNaN(this.boundingBox.min.z)) {

      console.error('THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this);

    }

  },

  computeBoundingSphere: function () {

    if (this.boundingSphere === null) {

      this.boundingSphere = new Sphere();

    }

    const position = this.attributes.position;
    const morphAttributesPosition = this.morphAttributes.position;

    if (position && position.isGLBufferAttribute) {

      console.error('THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this);

      this.boundingSphere.set(new Vector3(), Infinity);

      return;

    }

    if (position) {

      // first, find the center of the bounding sphere

      const center = this.boundingSphere.center;

      _box.setFromBufferAttribute(position);

      // process morph attributes if present

      if (morphAttributesPosition) {

        for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {

          const morphAttribute = morphAttributesPosition[i];
          _boxMorphTargets.setFromBufferAttribute(morphAttribute);

          if (this.morphTargetsRelative) {

            _vector.addVectors(_box.min, _boxMorphTargets.min);
            _box.expandByPoint(_vector);

            _vector.addVectors(_box.max, _boxMorphTargets.max);
            _box.expandByPoint(_vector);

          } else {

            _box.expandByPoint(_boxMorphTargets.min);
            _box.expandByPoint(_boxMorphTargets.max);

          }

        }

      }

      _box.getCenter(center);

      // second, try to find a boundingSphere with a radius smaller than the
      // boundingSphere of the boundingBox: sqrt(3) smaller in the best case

      let maxRadiusSq = 0;

      for (let i = 0, il = position.count; i < il; i++) {

        _vector.fromBufferAttribute(position, i);

        maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector));

      }

      // process morph attributes if present

      if (morphAttributesPosition) {

        for (let i = 0, il = morphAttributesPosition.length; i < il; i++) {

          const morphAttribute = morphAttributesPosition[i];
          const morphTargetsRelative = this.morphTargetsRelative;

          for (let j = 0, jl = morphAttribute.count; j < jl; j++) {

            _vector.fromBufferAttribute(morphAttribute, j);

            if (morphTargetsRelative) {

              _offset.fromBufferAttribute(position, j);
              _vector.add(_offset);

            }

            maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector));

          }

        }

      }

      this.boundingSphere.radius = Math.sqrt(maxRadiusSq);

      if (isNaN(this.boundingSphere.radius)) {

        console.error('THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this);

      }

    }

  },

  computeFaceNormals: function () {

    // backwards compatibility

  },

  computeVertexNormals: function () {

    const index = this.index;
    const positionAttribute = this.getAttribute('position');

    if (positionAttribute !== undefined) {

      let normalAttribute = this.getAttribute('normal');

      if (normalAttribute === undefined) {

        normalAttribute = new BufferAttribute(new Float32Array(positionAttribute.count * 3), 3);
        this.setAttribute('normal', normalAttribute);

      } else {

        // reset existing normals to zero

        for (let i = 0, il = normalAttribute.count; i < il; i++) {

          normalAttribute.setXYZ(i, 0, 0, 0);

        }

      }

      const pA = new Vector3(), pB = new Vector3(), pC = new Vector3();
      const nA = new Vector3(), nB = new Vector3(), nC = new Vector3();
      const cb = new Vector3(), ab = new Vector3();

      // indexed elements

      if (index) {

        for (let i = 0, il = index.count; i < il; i += 3) {

          const vA = index.getX(i + 0);
          const vB = index.getX(i + 1);
          const vC = index.getX(i + 2);

          pA.fromBufferAttribute(positionAttribute, vA);
          pB.fromBufferAttribute(positionAttribute, vB);
          pC.fromBufferAttribute(positionAttribute, vC);

          cb.subVectors(pC, pB);
          ab.subVectors(pA, pB);
          cb.cross(ab);

          nA.fromBufferAttribute(normalAttribute, vA);
          nB.fromBufferAttribute(normalAttribute, vB);
          nC.fromBufferAttribute(normalAttribute, vC);

          nA.add(cb);
          nB.add(cb);
          nC.add(cb);

          normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z);
          normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z);
          normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z);

        }

      } else {

        // non-indexed elements (unconnected triangle soup)

        for (let i = 0, il = positionAttribute.count; i < il; i += 3) {

          pA.fromBufferAttribute(positionAttribute, i + 0);
          pB.fromBufferAttribute(positionAttribute, i + 1);
          pC.fromBufferAttribute(positionAttribute, i + 2);

          cb.subVectors(pC, pB);
          ab.subVectors(pA, pB);
          cb.cross(ab);

          normalAttribute.setXYZ(i + 0, cb.x, cb.y, cb.z);
          normalAttribute.setXYZ(i + 1, cb.x, cb.y, cb.z);
          normalAttribute.setXYZ(i + 2, cb.x, cb.y, cb.z);

        }

      }

      this.normalizeNormals();

      normalAttribute.needsUpdate = true;

    }

  },

  merge: function (geometry, offset) {

    if (!(geometry && geometry.isBufferGeometry)) {

      console.error('THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry);
      return;

    }

    if (offset === undefined) {

      offset = 0;

      console.warn(
        'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. '
        + 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'
      );

    }

    const attributes = this.attributes;

    for (const key in attributes) {

      if (geometry.attributes[key] === undefined) continue;

      const attribute1 = attributes[key];
      const attributeArray1 = attribute1.array;

      const attribute2 = geometry.attributes[key];
      const attributeArray2 = attribute2.array;

      const attributeOffset = attribute2.itemSize * offset;
      const length = Math.min(attributeArray2.length, attributeArray1.length - attributeOffset);

      for (let i = 0, j = attributeOffset; i < length; i++, j++) {

        attributeArray1[j] = attributeArray2[i];

      }

    }

    return this;

  },

  normalizeNormals: function () {

    const normals = this.attributes.normal;

    for (let i = 0, il = normals.count; i < il; i++) {

      _vector.fromBufferAttribute(normals, i);

      _vector.normalize();

      normals.setXYZ(i, _vector.x, _vector.y, _vector.z);

    }

  },

  toNonIndexed: function () {

    function convertBufferAttribute(attribute, indices) {

      const array = attribute.array;
      const itemSize = attribute.itemSize;
      const normalized = attribute.normalized;

      const array2 = new array.constructor(indices.length * itemSize);

      let index = 0, index2 = 0;

      for (let i = 0, l = indices.length; i < l; i++) {

        index = indices[i] * itemSize;

        for (let j = 0; j < itemSize; j++) {

          array2[index2++] = array[index++];

        }

      }

      return new BufferAttribute(array2, itemSize, normalized);

    }

    //

    if (this.index === null) {

      console.warn('THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.');
      return this;

    }

    const geometry2 = new BufferGeometry();

    const indices = this.index.array;
    const attributes = this.attributes;

    // attributes

    for (const name in attributes) {

      const attribute = attributes[name];

      const newAttribute = convertBufferAttribute(attribute, indices);

      geometry2.setAttribute(name, newAttribute);

    }

    // morph attributes

    const morphAttributes = this.morphAttributes;

    for (const name in morphAttributes) {

      const morphArray = [];
      const morphAttribute = morphAttributes[name]; // morphAttribute: array of Float32BufferAttributes

      for (let i = 0, il = morphAttribute.length; i < il; i++) {

        const attribute = morphAttribute[i];

        const newAttribute = convertBufferAttribute(attribute, indices);

        morphArray.push(newAttribute);

      }

      geometry2.morphAttributes[name] = morphArray;

    }

    geometry2.morphTargetsRelative = this.morphTargetsRelative;

    // groups

    const groups = this.groups;

    for (let i = 0, l = groups.length; i < l; i++) {

      const group = groups[i];
      geometry2.addGroup(group.start, group.count, group.materialIndex);

    }

    return geometry2;

  },

  toJSON: function () {

    const data = {
      metadata: {
        version: 4.5,
        type: 'BufferGeometry',
        generator: 'BufferGeometry.toJSON'
      }
    };

    // standard BufferGeometry serialization

    data.uuid = this.uuid;
    data.type = this.type;
    if (this.name !== '') data.name = this.name;
    if (Object.keys(this.userData).length > 0) data.userData = this.userData;

    if (this.parameters !== undefined) {

      const parameters = this.parameters;

      for (const key in parameters) {

        if (parameters[key] !== undefined) data[key] = parameters[key];

      }

      return data;

    }

    data.data = {attributes: {}};

    const index = this.index;

    if (index !== null) {

      data.data.index = {
        type: index.array.constructor.name,
        array: Array.prototype.slice.call(index.array)
      };

    }

    const attributes = this.attributes;

    for (const key in attributes) {

      const attribute = attributes[key];

      const attributeData = attribute.toJSON(data.data);

      if (attribute.name !== '') attributeData.name = attribute.name;

      data.data.attributes[key] = attributeData;

    }

    const morphAttributes = {};
    let hasMorphAttributes = false;

    for (const key in this.morphAttributes) {

      const attributeArray = this.morphAttributes[key];

      const array = [];

      for (let i = 0, il = attributeArray.length; i < il; i++) {

        const attribute = attributeArray[i];

        const attributeData = attribute.toJSON(data.data);

        if (attribute.name !== '') attributeData.name = attribute.name;

        array.push(attributeData);

      }

      if (array.length > 0) {

        morphAttributes[key] = array;

        hasMorphAttributes = true;

      }

    }

    if (hasMorphAttributes) {

      data.data.morphAttributes = morphAttributes;
      data.data.morphTargetsRelative = this.morphTargetsRelative;

    }

    const groups = this.groups;

    if (groups.length > 0) {

      data.data.groups = JSON.parse(JSON.stringify(groups));

    }

    const boundingSphere = this.boundingSphere;

    if (boundingSphere !== null) {

      data.data.boundingSphere = {
        center: boundingSphere.center.toArray(),
        radius: boundingSphere.radius
      };

    }

    return data;

  },

  clone: function () {

    /*
     // Handle primitives

     const parameters = this.parameters;

     if ( parameters !== undefined ) {

     const values = [];

     for ( const key in parameters ) {

     values.push( parameters[ key ] );

     }

     const geometry = Object.create( this.constructor.prototype );
     this.constructor.apply( geometry, values );
     return geometry;

     }

     return new this.constructor().copy( this );
     */

    return new BufferGeometry().copy(this);

  },

  copy: function (source) {

    // reset

    this.index = null;
    this.attributes = {};
    this.morphAttributes = {};
    this.groups = [];
    this.boundingBox = null;
    this.boundingSphere = null;

    // used for storing cloned, shared data

    const data = {};

    // name

    this.name = source.name;

    // index

    const index = source.index;

    if (index !== null) {

      this.setIndex(index.clone(data));

    }

    // attributes

    const attributes = source.attributes;

    for (const name in attributes) {

      const attribute = attributes[name];
      this.setAttribute(name, attribute.clone(data));

    }

    // morph attributes

    const morphAttributes = source.morphAttributes;

    for (const name in morphAttributes) {

      const array = [];
      const morphAttribute = morphAttributes[name]; // morphAttribute: array of Float32BufferAttributes

      for (let i = 0, l = morphAttribute.length; i < l; i++) {

        array.push(morphAttribute[i].clone(data));

      }

      this.morphAttributes[name] = array;

    }

    this.morphTargetsRelative = source.morphTargetsRelative;

    // groups

    const groups = source.groups;

    for (let i = 0, l = groups.length; i < l; i++) {

      const group = groups[i];
      this.addGroup(group.start, group.count, group.materialIndex);

    }

    // bounding box

    const boundingBox = source.boundingBox;

    if (boundingBox !== null) {

      this.boundingBox = boundingBox.clone();

    }

    // bounding sphere

    const boundingSphere = source.boundingSphere;

    if (boundingSphere !== null) {

      this.boundingSphere = boundingSphere.clone();

    }

    // draw range

    this.drawRange.start = source.drawRange.start;
    this.drawRange.count = source.drawRange.count;

    // user data

    this.userData = source.userData;

    return this;

  },

  dispose: function () {

    this.dispatchEvent({type: 'dispose'});

  }

});


export {BufferGeometry};
